import socket
import thread
from threading import Lock
import time
from glob import glob
import random
import re
import inspect

from randomEffects import *

NUM_OF_CLIENTS = 100
MAX_JOBS_PER_CLIENT = 50

PUT_TEMPLATE = file(r'templates/putTemplate.txt','rb').read()
GET_TEMPLATE = file(r'templates/getTemplate.txt','rb').read()
POST_TEMPLATE = file(r'templates/postTemplate.txt','rb').read()
JOB_TEMPLATE = file(r'templates/jobTemplate.txt','rb').read()

UPLOAD_PATH = "/upload"
GET_PHOTO_PATH = "/photos/%d?rep=%d"
POST_PHOTO_PATH = "/photos/%d"
POST_SHUTDOWN = "/shutdown"
UPLOAD_RESPONSE_RE = file('templates/uploadPhotoRegex.txt','rb').read()
UPLOAD_JOB_RESPONSE_RE = file('templates/uploadJobRegex.txt','rb').read()

LINE_BREAK = "\r\n"
#LINE_BREAK = "\n"

DONT_DIE_ON_EXCEPTIONS = True

UPLOADED = {}
UPLOADED_lock = Lock()

PENDING = []
PENDING_lock = Lock()


def synchronized(lock):
    """ Synchronization decorator. """
    def wrap(f):
        def new_function(*args, **kw):
            lock.acquire()
            try:
                return f(*args, **kw)
            finally:
                lock.release()
        return new_function
    return wrap

def Unlethal(message):
    if DONT_DIE_ON_EXCEPTIONS:
        print "Unlethal: %s" % message
    else:
        raise Exception(message)    

@synchronized(PENDING_lock)    
def addToPending(id,rep):
    global PENDING
    PENDING.append((id,rep))

@synchronized(PENDING_lock)    
def popFromPending():
    global PENDING
    return PENDING.pop()
    
@synchronized(UPLOADED_lock)
def addToUploaded(new_id, image):
    global UPLOADED
    if new_id in UPLOADED:
        Unlethal("new_id already in list")
    UPLOADED[new_id] = image

@synchronized(UPLOADED_lock)    
def getUploaded(id):
    global UPLOADED
    return UPLOADED[id]

@synchronized(UPLOADED_lock)    
def getRandomUploaded():
    global UPLOADED
    id = random.choice(UPLOADED.keys())
    return id,UPLOADED[id]
    
class HttpResponse(object):
    def __init__(self,sck):
        data = ""
        # read headers
        while data.find(LINE_BREAK*2)==-1:
            data += sck.recv(1000)
        headers_raw = data.split(LINE_BREAK*2)[0]
        initial_request_line = headers_raw.splitlines()[0]
        version, response_code, reason = initial_request_line.split(" ",2)
        headers = {}
        for line in headers_raw.splitlines()[1:]:
            name,value = line.split(":",1)
            value = value.strip()
            headers[name] = value
               
        # check if more data need to be recieved
        start_of_body = data.find(LINE_BREAK*2) + len(LINE_BREAK*2)
        while start_of_body+int(headers["Content-Length"]) > len(data):
            data += sck.recv(start_of_body+int(headers["Content-Length"]) - len(data))
        
        
        self.body = data[start_of_body:]
        self.headers = headers
        self.raw_data = data
        self.version = version
        self.response_code = int(response_code)
        self.reason = reason
        
        # always validate
        if self.version != "HTTP/1.1":
            Unlethal("wrong http version : %s" % self.version)
        return
    
def validateHeader(headers,name,value):
    caller = inspect.stack()[1][3]
    if headers[name] != value:
        Unlethal("%s: wrong '%s' header : '%s' should be '%s'" % (caller, name, headers[name], value))
    
def validateNoMoreHeaders(headers, valid):
    caller = inspect.stack()[1][3]
    for i in headers:
        if i not in valid:
            Unlethal("%s: invalid '%s' header" % (caller, i))
    
def uploadRandomImage(server_address, server_port):
    options = glob("photos/*.jpg")
    choice = random.choice(options)
    image = file(choice,'rb').read()
    print "Uploading random image %s" % choice
    put_request = PUT_TEMPLATE % (UPLOAD_PATH, server_address, server_port, len(image), image)
    
    sck = socket.socket()
    sck.connect((server_address, server_port))
    sck.send(put_request)
    response = HttpResponse(sck)
    validateHeader(response.headers, "Server", "%s:%d" % (server_address, server_port))
    validateHeader(response.headers, "Content-Type", "text/html; charset=utf-8" )
    validateNoMoreHeaders(response.headers, ["Server", "Content-Type", "Content-Length"])

    if response.response_code != 201:
        Unlethal("uploadRandomImage: Invalid server response code %d" % response.response_code)
    if response.reason != "Created":
        Unlethal("uploadRandomImage: Invalid server reason %s" % response.reason)    

    result = re.search(UPLOAD_RESPONSE_RE, response.body)
    file('uploadRandomImage.response.body','wb').write(response.body)
    if not(result):
        print response.body
        raise Exception("Invalid upload response")        
        
    if response.headers["Server"] != result.groups()[0]:
        print result.groups()
        Unlethal("uploadRandomImage: Invalid server response")
    new_id = int(result.groups()[1])
    for i in xrange(4):
        if new_id != int(result.groups()[1+i]):
            raise Exception("uploadRandomImage: Diffrent id's but should be the same")
    print "uploadRandomImage: finished all ok (new id %s)" % new_id
    addToUploaded(new_id, image)
    return new_id

def downloadRandomImage(server_address, server_port):
    id,image = getRandomUploaded()
    print "Downloading random image %d" % id
    path = GET_PHOTO_PATH % (id,0)
    get_request = GET_TEMPLATE % (path, server_address, server_port, "image/jpeg")
    
    sck = socket.socket()
    sck.connect((server_address, server_port))
    sck.send(get_request)
    response = HttpResponse(sck)
    
    if response.response_code != 200:
        Unlethal("uploadRandomImage: Invalid server response code %d" % response.response_code)
    if response.reason != "OK":
        Unlethal("uploadRandomImage: Invalid server reason %s" % response.reason)      
    
    validateHeader(response.headers, "Server", "%s:%d" % (server_address, server_port))
    validateHeader(response.headers, "Content-Type", "image/jpeg" )
    validateNoMoreHeaders(response.headers, ["Server", "Content-Type", "Content-Length"])
    
    if response.body != image:
        Unlethal("downloadRandomImage: Invalid server response")
    print "Downloading random image DONE"
    return
    
def postShutdown(server_address, server_port):
    data = ""
    post_request = POST_TEMPLATE % (POST_SHUTDOWN, server_address, server_port, len(data), data)
    sck = socket.socket()
    sck.connect((server_address, server_port))
    sck.send(post_request)
    response = HttpResponse(sck)

    if response.response_code != 200:
        Unlethal("postShotdown: Invalid server response code %d" % response.response_code)
    time.sleep(1)
    try:
        sck = socket.socket()
        sck.connect((server_address, server_port))
        Unlethal("Shutdown failed")
    except:
        pass
    
    
def postRandomJob(server_address, server_port):
    id,image = getRandomUploaded()
    print "Post job to random image id %d" % id
    
    xml = JOB_TEMPLATE.replace("INPUT_REP_MAGIC","0").replace("EFFECT_MAGIC",genRandomEffects()).replace("\r","")
    #xml = file("job-test-1.xml",'rb').read()
    path = POST_PHOTO_PATH % (id)
    post_request = POST_TEMPLATE % (path, server_address, server_port, len(xml), xml)
    
    sck = socket.socket()
    sck.connect((server_address, server_port))
    sck.send(post_request)
    response = HttpResponse(sck)

    if response.response_code != 202:
        Unlethal("postRandomJob: Invalid server response code %d" % response.response_code)
    if response.reason != "Created":
        Unlethal("postRandomJob: Invalid server reason %s" % response.reason)     
    
    validateHeader(response.headers, "Server", "%s:%d" % (server_address, server_port))
    validateHeader(response.headers, "Content-Type", "text/html; charset=utf-8" )
    validateNoMoreHeaders(response.headers, ["Server", "Content-Type", "Content-Length"])    
    
    result = re.search(UPLOAD_JOB_RESPONSE_RE, response.body)
    if not(result):
        print response.body
        file('postRandomJob.response.body','wb').write(response.body)
        raise Exception("Invalid upload response")        

    """
    if response.headers["Server"] != result.groups()[0]:
        Unlethal("uploadRandomImage: Invalid server response")
    new_id = int(result.groups()[1])
    for i in xrange(4):
        if new_id != int(result.groups()[1+i]):
            raise Exception("uploadRandomImage: Diffrent id's but should be the same")    
    new_rep = int(result.groups()[5])
    """
    old_id = int(result.groups()[0])
    if old_id != id:
        Unlethal("postRandomJob: Invalid id in response %d" % old_id)     
    new_rep = int(result.groups()[1])
    
    addToPending(id,new_rep)
    return new_rep

def waitUntilJobDone(server_address, server_port):
    id, rep = popFromPending()
    for i in xrange(10):
        
        print "Trying to downloading random pending job id = %d, rep = %d" % (id, rep)
        path = GET_PHOTO_PATH % (id,rep)
        get_request = GET_TEMPLATE % (path, server_address, server_port, "image/jpeg")
        
        sck = socket.socket()
        sck.connect((server_address, server_port))
        sck.send(get_request)
        response = HttpResponse(sck)
        
        if response.response_code == 206:
            if response.reason != "PENDING":
                Unlethal("waitUntilJobDone: Invalid server reason %s" % response.reason)      
            print "Job is still pending, sleep for 10 seconds"
            time.sleep(10)
        elif response.response_code == 200:
            if response.reason != "OK":
                Unlethal("waitUntilJobDone: Invalid server reason %s" % response.reason)      
            validateHeader(response.headers, "Server", "%s:%d" % (server_address, server_port))
            validateHeader(response.headers, "Content-Type", "image/jpeg" )
            validateNoMoreHeaders(response.headers, ["Server", "Content-Type", "Content-Length"])
            
            if len(response.body) < 10:
                Unlethal("waitUntilJobDone: response.body is too small")
            print "Downloading random image DONE"                       
            return
            
        else:    
            Unlethal("waitUntilJobDone: Invalid server response code %d" % response.response_code)
    Unlethal("waitUntilJobDone: Job is pending too long id: %d, rep:%d" % (id,rep))
    addToPending(id, rep)
    
def clientThreadFunc(server_address, server_port):
    for i in xrange(random.randint(10,MAX_JOBS_PER_CLIENT)):
        uploadRandomImage(server_address, server_port)
        downloadRandomImage(server_address, server_port)
        postRandomJob(server_address, server_port)
        waitUntilJobDone(server_address, server_port)
    #postShutdown(server_address, server_port)

def main(server_address, server_port):
    for i in xrange(NUM_OF_CLIENTS):
        print "Starting %d clientThreadFunc" % i
        thread.start_new_thread(clientThreadFunc,(server_address, server_port))
        time.sleep(2)
    print "waiting for threads to finish"
    time.sleep(1000)

if __name__ == "__main__":
    main("Photohut",7070)